/**
 * This file contain a lot of functions, names doWord1Word2
 * Such a function, execute the command begin with "word1 word2"
 * @param tournament the Tournament to execute the command on
 * @param out the output file
 * Note: strtok internal iterators should be in the first
 * parameter of the command (as if it just parsed "word1 word2")
 * @return
 * true, in case of success, or a treatable error
 * false, in case of fatal error
 */

#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "mtm_wet3.h"
#include "list.h"
#include "parse.h"
#include "dish.h"
#include "chef.h"
#include "judge.h"
#include "tournament.h"

static const char *delimiter=" \t\n";

static bool doReset(Tournament tournament) {
	tournamentClear(tournament);
	return true;
}

static bool doChefAdd(Tournament tournament) {
	const char *name=strtok(NULL,delimiter);
	Chef chef=chefCreate(name,NULL);
	TournamentResult result=tournamentAddChef(tournament,chef);
	chefDestroy(chef);
	switch (result) {
	case TOURNAMENT_INVALID_ARGUMENT:
	case TOURNAMENT_OUT_OF_MEMORY:
		mtmPrintErrorMessage(stderr,MTM_OUT_OF_MEMORY);
		return false;
		break;
	case TOURNAMENT_CHEF_ALREADY_EXISTS:
		mtmPrintErrorMessage(stderr,MTM_CHEF_ALREADY_EXISTS);
	case TOURNAMENT_SUCCESS:
	default:
		return true;
		break;
	}
}

static bool doChefAddDish(Tournament tournament) {
	const char *chefName=strtok(NULL,delimiter);
	const char *dishName=strtok(NULL,delimiter);
	int type,sweetness,sourness,saltiness,priority;
	sscanf(strtok(NULL,delimiter),"%d",&type);
	sscanf(strtok(NULL,delimiter),"%d",&sweetness);
	sscanf(strtok(NULL,delimiter),"%d",&sourness);
	sscanf(strtok(NULL,delimiter),"%d",&saltiness);
	sscanf(strtok(NULL,delimiter),"%d",&priority);
	Chef chef=tournamentGetChef(tournament,chefName);
	if (!chef) {
		mtmPrintErrorMessage(stderr,MTM_CHEF_NOT_FOUND);
		return true;
	}
	DishResult dishResult;
	Dish dish=dishCreate(dishName,type-1,sweetness,
			sourness,saltiness,&dishResult);
	if (dishResult==DISH_INVALID_ARGUMENT) {
		mtmPrintErrorMessage(stderr,MTM_INVALID_INPUT_COMMAND_PARAMETERS);
		return true;
	}
	if (dishResult==DISH_OUT_OF_MEMORY) {
		mtmPrintErrorMessage(stderr,MTM_OUT_OF_MEMORY);
		return false;
	}
	ChefResult chefResult;
	chefAddDish(chef,dish,priority,&chefResult);
	dishDestroy(dish);
	switch (chefResult) {
	case CHEF_OUT_OF_MEMORY:
		mtmPrintErrorMessage(stderr,MTM_OUT_OF_MEMORY);
		return false;
		break;
	case CHEF_INVALID_ARGUMENT:
		mtmPrintErrorMessage(stderr,MTM_INVALID_INPUT_COMMAND_PARAMETERS);
	case CHEF_SUCCESS:
	default:
		return true;
		break;
	}
}

//helper function, for List of strings
static ListElement copyString(ListElement str){
	char *copy=malloc(strlen(str)+1);
	return copy?strcpy(copy,str):NULL;
}

//helper function, for List of strings
static void freeString(ListElement str){
	free(str);
}

static bool doChefCompete(Tournament tournament, FILE *out) {
	Chef chef1=tournamentGetChef(tournament,strtok(NULL,delimiter));
	Chef chef2=tournamentGetChef(tournament,strtok(NULL,delimiter));
	List names=listCreate(copyString,freeString);
	if (!names) {
		mtmPrintErrorMessage(stderr,MTM_OUT_OF_MEMORY);
		return false;
	}
	TournamentResult result;
	int diff=tournamentCompeteChefs(tournament,chef1,chef2,names,&result);
	LIST_FOREACH(char *,judge,names) {
		mtmPrintJudgeResignationMessage(out,judge);
	}
	listDestroy(names);
	switch (result) {
	case TOURNAMENT_INVALID_ARGUMENT:
		mtmPrintErrorMessage(stderr,MTM_CHEF_NOT_FOUND);
		return true;
		break;
	case TOURNAMENT_SAME_CHEF:
		mtmPrintErrorMessage(stderr,MTM_SAME_CHEF);
		return true;
		break;
	case TOURNAMENT_NO_JUDGES:
		mtmPrintErrorMessage(stderr,MTM_NO_JUDGES);
		return true;
		break;
	case TOURNAMENT_CHEF_HAS_NO_DISHES:
		mtmPrintErrorMessage(stderr,MTM_CHEF_HAS_NO_DISHES);
		return true;
		break;
	case TOURNAMENT_OUT_OF_MEMORY:
		mtmPrintErrorMessage(stderr,MTM_OUT_OF_MEMORY);
		return false;
		break;
	case TOURNAMENT_SUCCESS:
	default:
		break;
	}
	char *chef1name=(char*)chefGetName(chef1);
	char *chef2name=(char*)chefGetName(chef2);
	if (diff>0) {
		mtmPrintWinningAndLosingChefs(out,chef1name,chef2name);
	} else if (diff<0) {
		mtmPrintWinningAndLosingChefs(out,chef2name,chef1name);
	} else {
		mtmPrintTieBetweenTwoChefs(out,chef1name,chef2name);
	}
	return true;
}

static bool doChefLeading(Tournament tournament, FILE *out) {
	Chef chef=tournamentGetLeadingChef(tournament);
	if (!chef) {
		mtmPrintErrorMessage(stderr,MTM_NO_CHEFS);
	} else {
		mtmPrintLeadingChef(out,(char*)chefGetName(chef),chefGetScore(chef));
	}
	return true;
}

static bool doChefTopDish(Tournament tournament, FILE *out) {
	Chef chef=tournamentGetChef(tournament,strtok(NULL,delimiter));
	if (!chef) {
		mtmPrintErrorMessage(stderr,MTM_CHEF_NOT_FOUND);
		return true;
	}
	Dish dish=chefGetFavoriteDish(chef,NULL);
	if (!dish) {
		mtmPrintErrorMessage(stderr,MTM_CHEF_HAS_NO_DISHES);
		return true;
	}
	mtmPrintTopDish(out,(char*)chefGetName(chef),(char*)dishGetName(dish));
	return true;
}

//taste preferences of judges
static int taste1(Dish dish1,Dish dish2) {
	int dish1edible=dishIsEdible(dish1);
	int dish2edible=dishIsEdible(dish2);
	if (!dish1edible || !dish2edible) {
		return dish1edible-dish2edible;
	}
	return dishGetSweetness(dish1)-dishGetSweetness(dish2);
}

static int taste2(Dish dish1,Dish dish2) {
	int dish1edible=dishIsEdible(dish1);
	int dish2edible=dishIsEdible(dish2);
	if (!dish1edible || !dish2edible) {
		return dish1edible-dish2edible;
	}
	return dishGetSaltiness(dish1)-dishGetSaltiness(dish2);
}

static int taste3(Dish dish1,Dish dish2) {
	int dish1edible=dishIsEdible(dish1);
	int dish2edible=dishIsEdible(dish2);
	if (!dish1edible || !dish2edible) {
		return dish1edible-dish2edible;
	}
	DishType dish1type=dishGetType(dish1);
	DishType dish2type=dishGetType(dish2);
	if (dish1type==DESERT && dish2type!=DESERT) {
		return 1;
	} else if (dish1type!=DESERT && dish2type==DESERT) {
		return -1;
	} else if (dish1type==MAIN_COURSE && dish2type==ENTREE) {
		return 1;
	} else if (dish1type==ENTREE && dish2type==MAIN_COURSE) {
		return -1;
	}
	return dishGetSourness(dish2)-dishGetSourness(dish1);
}

static const JudgeTasteFunction tastes[]={taste1,taste2,taste3};

static bool doJudgeAdd(Tournament tournament) {
	const char *name=strtok(NULL,delimiter);
	int taste;
	sscanf(strtok(NULL,delimiter),"%d",&taste);
	if (taste<=0 || taste>(int)(sizeof(tastes)/sizeof(*tastes))) {
		mtmPrintErrorMessage(stderr,MTM_INVALID_INPUT_COMMAND_PARAMETERS);
		return true;
	}
	Judge judge=judgeCreate(name,tastes[taste-1],NULL);
	TournamentResult result=tournamentAddJudge(tournament,judge);
	judgeDestroy(judge);
	if (result!=TOURNAMENT_SUCCESS) {
		mtmPrintErrorMessage(stderr,MTM_OUT_OF_MEMORY);
		return false;
	}
	return true;
}

static bool doJudgePrint(Tournament tournament, FILE *out) {
	List list=listCreate(copyString,freeString);
	if (!list) {
		mtmPrintErrorMessage(stderr,MTM_OUT_OF_MEMORY);
		return false;
	}
	TournamentResult result=tournamentGetJudgesNames(tournament,list);
	if (result==TOURNAMENT_OUT_OF_MEMORY) {
		mtmPrintErrorMessage(stderr,MTM_OUT_OF_MEMORY);
		listDestroy(list);
		return false;
	}
	int n=listGetSize(list);
	if (n==0) {
		mtmPrintErrorMessage(stderr,MTM_NO_JUDGES);
		listDestroy(list);
		return true;
	}
	char **names=malloc(n*sizeof(*names));
	if (!names) {
		mtmPrintErrorMessage(stderr,MTM_OUT_OF_MEMORY);
		listDestroy(list);
		return false;
	}
	int i=0;
	LIST_FOREACH(char *,judge,list) {
		names[i++]=judge;
	}
	mtmPrintAllJudges(out,names,n);
	free(names);
	listDestroy(list);
	return true;
}

bool parse_input(char buffer[], FILE *out, Tournament tournament) {
	const char *word1=strtok(buffer,delimiter);
	if (word1==NULL || word1[0]=='#') {
		return true;
	}
	if (strcmp(word1,"reset")==0) {
		return doReset(tournament);
	}
	const char *word2=strtok(NULL,delimiter);
	if (word2==NULL) {
		return false;
	}
	if (strcmp(word1,"chef")==0) {
		if (strcmp(word2,"add")==0) {
			return doChefAdd(tournament);
		}
		if (strcmp(word2,"add-dish")==0) {
			return doChefAddDish(tournament);
		}
		if (strcmp(word2,"compete")==0) {
			return doChefCompete(tournament,out);
		}
		if (strcmp(word2,"leading")==0) {
			return doChefLeading(tournament,out);
		}
		if (strcmp(word2,"top-dish")==0) {
			return doChefTopDish(tournament,out);
		}
	}
	if (strcmp(word1,"judge")==0) {
		if (strcmp(word2,"add")==0) {
			return doJudgeAdd(tournament);
		}
		if (strcmp(word2,"print")==0) {
			return doJudgePrint(tournament,out);
		}
	}
	return false;
}
